package webserver;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.ArrayList;

/**
 * Abstract Class Request
 * Containing all needed Data for one Request
 * 
 */
// <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
// #[regen=yes,id=DCE.58E26EAE-D8A5-CFE0-D772-04BF774872DD]
// </editor-fold> 
public abstract class Request {

	// Variables
	// <editor-fold defaultstate="collapsed" desc=" UML Marker ">
	// #[regen=yes,id=DCE.A795DBB0-E2C9-101C-1EBF-9FB78F10C76E]
	// </editor-fold>
	/**
	 * Representing the Socket from which the Request was received
	 */
	protected Socket socket;
	/**
	 * Byte[] containing the whole header for Response
	 */
	protected byte[] header;
	// <editor-fold defaultstate="collapsed" desc=" UML Marker ">
	// #[regen=yes,id=DCE.9E4F0C4B-3746-C660-046B-05328CFED55D]
	// </editor-fold>
	/**
	 * Byte[] containing the whole data for Response
	 */
	protected byte[] data;
	/**
	 * The complete input read from Socket (Request)
	 */
	protected ArrayList<String> input;
	/**
	 * The relative path which was requested
	 */
	protected String relPath;
	/**
	 * Should the connection be kept alive? (Connection: keep-alive header)
	 */
	protected boolean keepalive;
	
	private boolean active = true;

	/**
	 * Enum representing the current state of the reading process
	 * HEADER: reading header entries
	 * DATA: reading content
	 */
	protected enum readStateEnum {

		HEADER, DATA
	};

	/**
	 * Request Default Constructor
	 * @param input - A list with all lines read from input
	 */
	// <editor-fold defaultstate="collapsed" desc=" UML Marker ">
	// #[regen=yes,id=DCE.4AFB5E72-2FB3-99D1-8496-15B21A951896]
	// </editor-fold>
	public Request(ArrayList<String> input) {
		this.input = input;
		this.header = null;
		this.data = null;
	}

	/**
	 * Get the Socket for this Request
	 * @return Socket
	 */
	// <editor-fold defaultstate="collapsed" desc=" UML Marker ">
	// #[regen=yes,regenBody=yes,id=DCE.1658125A-6178-345B-D966-601B90B38BAC]
	// </editor-fold>
	public Socket getSocket() {
		return socket;
	}

	/**
	 * Set the Socket used for this Request
	 * @param val - Socket
	 */
	// <editor-fold defaultstate="collapsed" desc=" UML Marker ">
	// #[regen=yes,regenBody=yes,id=DCE.171344FB-5417-A09F-5ADF-66D1128A5E6A]
	// </editor-fold>
	public void setSocket(Socket val) {
		this.socket = val;
	}

	/**
	 * Process the input and get header / data lines
	 * Abstract Method, must be implemented in implementations
	 * @throws IllegalStateException
	 */
	// <editor-fold defaultstate="collapsed" desc=" UML Marker ">
	// #[regen=yes,id=DCE.895D1BF3-22FB-1E37-2F41-365512FF88F4]
	// </editor-fold>
	public abstract void process() throws IllegalStateException;

	/**
	 * Get the Data for Response
	 * @return byte[] - Response data (can be null)
	 */
	// <editor-fold defaultstate="collapsed" desc=" UML Marker ">
	// #[regen=yes,regenBody=yes,id=DCE.B80A399A-5A49-B5E0-85AB-3D0456B35316]
	// </editor-fold>
	public byte[] getData() {
		return this.data; //Cache.getInstance().getFile(this.relPath);
	}

	/**
	 * Get the Header for Response
	 * @return byte[] - Response Header
	 */
	// <editor-fold defaultstate="collapsed" desc=" UML Marker ">
	// #[regen=yes,regenBody=yes,id=DCE.F160CE7F-672B-0CD3-74AA-279FFD7FFC37]
	// </editor-fold>
	public byte[] getHeader() {
		return this.header;
	}

	/**
	 * Parse a Request from the given Socket
	 * @param socket
	 * @return Request
	 */
	public static Request parseRequest(Socket socket) {
		ArrayList<String> tempInput = new ArrayList<String>();
		boolean http11 = false;
		readStateEnum state = readStateEnum.HEADER;
		int contentLength = -1;

		try {
			// Create BufferedReader to read Input
			BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

			// Read
			String line;
			// Read the Header
			while ((line = reader.readLine()) != null) {
				tempInput.add(line);

				if (state == readStateEnum.HEADER) {
					// Check for HTTP/1.1 Request in header
					if (!http11 && line.contains("HTTP/1.1")) {
						http11 = true;
					}

					// Get Content-Length, if set
					if (line.startsWith("Content-Length")) {
						String[] split = line.split(":");
						contentLength = Integer.parseInt(split[1].trim());
					}

					// Check when data section begins
					if (line.isEmpty()) {
						state = readStateEnum.DATA;
						break;
					}
				}

				// Abort when no new data has been found in Stream from Socket
				if (!reader.ready()) {
					break;
				}
			}

			if (state == readStateEnum.DATA && contentLength > 0) {
				StringBuffer strData = new StringBuffer(contentLength);
				char[] data = new char[contentLength];

				for (int i = 0; i < contentLength; i++) {
					i += reader.read(data, i, contentLength - i);

					// Abort when no new data has been found in Stream from Socket
					if (!reader.ready()) {
						break;
					}
				}

				strData.append(data);
				tempInput.add(strData.toString());
			}
		} catch (IOException io) {
			Logger.writeToLog("Failed to read Input Stream: " + io.toString());
			io.printStackTrace();
		}

		// Return the corresponding Request
		if (http11) {
			return new HTTP11Request(tempInput);
		} else {
			return new HTTP10Request(tempInput);
		}
	}

	/**
	 * Get the absolut Path to the requested file
	 * @return String - absolute Path
	 */
	public String getAbsPath() {
		String defaultDomain = "";
		if(this instanceof HTTP10Request) {
			defaultDomain = AppProperties.getInstance().getDefaultDomain() + "/";
		}
		return AppProperties.getInstance().getHomeDirectory() + "/" + defaultDomain + this.relPath;
	}

	/**
	 * Return a String representation of this Request
	 * @return String
	 */
	@Override
	public String toString() {
		String ret = "Request " + this.socket.getInetAddress() + ":" + this.socket.getPort();
		ret += "\nPath: " + this.relPath;
		return ret;
	}

	/**
	 * Generate the Response Header and Data
	 * @throws IllegalStateException
	 */
	public abstract void generateResponse() throws IllegalStateException;

	/**
	 * Check if the Connection for this Request should be kept alive
	 * @return boolean
	 */
	public synchronized boolean getKeepAlive() {
		return this.keepalive;
	}

	/**
	 * Set the state for keep-alive (persistent) for the Connection of this Request
	 * @param state
	 */
	public synchronized void setKeepAlive(boolean state) {
		this.keepalive = state;
	}

	/**
	 * Generate logfile entry for Combined Logfile Format
	 * @return String
	 */
	public abstract String getLogFileEntry();

	/**
	 * Get the Timeout when a persisten connection can be closed
	 * @return int
	 */
	public abstract int getPersistentTimeout();

	/**
	 * Closes the Connection
	 */
	public void finish() {
		if (!this.active) {
			return; // Already closed
		}
		
		try {
			this.active = false;
			this.socket.close();
		} catch (IOException ex) {
			Logger.writeToLog("IO Error closing Socket: " + ex.toString());
		}
	}

	/**
	 * Check whether the Connection is active
	 * @return bool
	 */
	public boolean isActive() {
		return this.active;
	}
}

